package io.gitee.chearnee.fw.common.utils;

import com.fasterxml.jackson.core.JsonGenerator;
import io.gitee.chearnee.fw.common.annotation.IBaseEnumDesc;
import io.gitee.chearnee.fw.common.annotation.IBaseEnumJson;
import io.gitee.chearnee.fw.common.model.IBaseEnum;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * enumUtil
 *
 * @author cn
 * @version 1.0
 * @description
 * @date 2022/1/26 19:59
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JacksonEnumUtils {

    private static class DescField {
        private Field field;
        private String descSuffixStr;
    }

    private static final Map<Class<? extends IBaseEnum<?>>, Map<String, IBaseEnum<?>>> ENUM_MAP = new ConcurrentHashMap<>();
    private static final Map<Class<? extends IBaseEnum<?>>, Field> ENUM_FIELD_MAP = new ConcurrentHashMap<>();
    private static final Map<Class<? extends IBaseEnum<?>>, DescField> ENUM_FIELD_DESC_MAP = new ConcurrentHashMap<>();


    /**
     * enum Serializer   out <----> in enum Deserializer
     *
     * @param findPropertyType
     * @param findKey
     * @return
     */
    @SneakyThrows
    public static IBaseEnum<?> getFieldCache(Class<? extends IBaseEnum<?>> findPropertyType,
                                             String findKey) {
        Map<String, ? extends IBaseEnum<?>> stringIBaseEnumMap = ENUM_MAP.get(findPropertyType);
        if (stringIBaseEnumMap != null) {
            return stringIBaseEnumMap.get(findKey);
        }
        Map<String, IBaseEnum<?>> tempMap = new ConcurrentHashMap<>();
        Optional<Field> tempField = Arrays.stream(findPropertyType.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(
                        IBaseEnumJson.class)).findFirst();
        if (tempField.isPresent()) {
            Field field = tempField.get();
            ReflectionUtils.makeAccessible(field);
            Object[] enumConstants = findPropertyType.getEnumConstants();
            for (Object enumConstant : enumConstants) {
                if (enumConstant instanceof IBaseEnum) {
                    Object o = field.get(enumConstant);
                    tempMap.put(o.toString(), (IBaseEnum<?>) enumConstant);
                }
            }
        }
        ENUM_MAP.put(findPropertyType, tempMap);
        return tempMap.get(findKey);
    }

    @SneakyThrows
    public static <T extends IBaseEnum<?>> Object writeObject(T value) {
        Class<?> findPropertyType = value.getClass();
        Field field = ENUM_FIELD_MAP.get(findPropertyType);
        if (field != null) {
            return field.get(value);
        }
        Optional<Field> tempField = Arrays.stream(findPropertyType.getDeclaredFields())
                .filter(f -> f.isAnnotationPresent(
                        IBaseEnumJson.class)).findFirst();
        if (tempField.isPresent()) {
            field = tempField.get();
            ReflectionUtils.makeAccessible(field);
            ENUM_FIELD_MAP.put((Class<? extends IBaseEnum<?>>) findPropertyType, field);
            return field.get(value);
        }
        return value.getValue();
    }

    @SneakyThrows
    public static <T extends IBaseEnum<?>> void writeObject(JsonGenerator gen, T value) {
        gen.writeObject(writeObject(value));
        Class<?> findPropertyType = value.getClass();
        DescField descField = ENUM_FIELD_DESC_MAP.get(findPropertyType);
        if (descField == null) {
            Optional<Field> descFieldOpt = Arrays.stream(findPropertyType.getDeclaredFields()).filter(e -> e.isAnnotationPresent(IBaseEnumDesc.class))
                    .findFirst();
            if (descFieldOpt.isPresent()) {
                Field field = descFieldOpt.get();
                ReflectionUtils.makeAccessible(field);
                IBaseEnumDesc mergedAnnotation = field.getAnnotation(IBaseEnumDesc.class);
                if (mergedAnnotation != null) {
                    String suffixStr = mergedAnnotation.value();
                    descField = new DescField();
                    descField.descSuffixStr = suffixStr;
                    descField.field = field;
                    ENUM_FIELD_DESC_MAP.put((Class<? extends IBaseEnum<?>>) findPropertyType, descField);

                }
            }
        }
        if (descField == null) {
            return;
        }
        Object iEnumValue = ReflectionUtils.getField(descField.field, value);
        if (iEnumValue == null) {
            return;
        }
        gen.writeObjectField(gen.getOutputContext().getCurrentName() + descField.descSuffixStr, iEnumValue);
    }
}
